/*
 * Copyright (c) 2008 VMware, Inc.
 * Copyright (c) 2009 John Pritchard, WTKX Project Group
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package wtkx;

import wtkx.co.ResourceCacheDictionary;
import wtkx.in.Awtk;
import wtkx.in.Screen;
import wtkx.ui.Direction;
import wtkx.ui.GV;
import wtkx.ui.Threads;
import wtkx.ui.Platform;

import jbxml.Dictionary;

import java.awt.Color;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.RenderingHints;
import java.awt.Toolkit;
import java.awt.event.PaintEvent;
import java.io.InputStream;
import java.io.IOException;
import java.net.URL;

/**
 * The root of the scene graph, it's instantiated by an {@link
 * wtkx.in.Applet Applet} or {@link wtkx.in.Frame Frame} and serves
 * those classes as an application manager.  It contains one or more
 * instances of {@link Window} which overlay each other in ascending Z
 * order.
 * 
 *
 * @author gbrown
 * @author jdp
 */
public final class Scene 
    extends Container 
    implements jbxml.Component.Root
{
    private final static String[][] InstallDefaults = {
        {"http://wtkx.org/1.0","wtkx"},
        {"http://wtkx.org/1.0/effects","wtkx.ef"},
        {"http://wtkx.org/1.0/proto","wtkx.ps"},
        {"http://www.w3.org/1999/xhtml","wtkx"}
    };
    private final static String[][] InstallOpts = {
        { "color","peal"},
        { "background-color","white"},
        { "selection-color","sele"},
        { "font","SansSerif 14"},
    };
    public final static Dictionary<String,String> Install(Dictionary<String,String> properties){
        for (int cc = 0, count = InstallDefaults.length; cc < count; cc++){
            String[] pair = InstallDefaults[cc];
            String name = pair[0];
            String value = pair[1];
            properties.put(name,value);
        }
        return InstallOpt(properties);
    }
    public final static Dictionary<String,String> InstallOpt(Dictionary<String,String> properties){
        for (int cc = 0, count = InstallOpts.length; cc < count; cc++){
            String[] pair = InstallOpts[cc];
            String name = pair[0];
            if (null == properties.get(name)){
                String value = pair[1];
                properties.put(name,value);
            }
        }
        return properties;
    }
    public final class Validator
        extends Object
        implements Runnable
    {
        Validator(){
            super();
        }
        public void run(){
            try {
                Scene.this.validate();
            }
            catch (Exception exc){
                exc.printStackTrace();
            }
        }
    }


    private Dictionary<String,String> properties;

    private java.awt.Container hostContainer;

    private wtkx.in.DisplayContainer hostContainerDc;

    private java.awt.Component hostComponent;

    private Awtk awtk;

    private ResourceCacheDictionary resourceCache = new ResourceCacheDictionary();

    private final Validator validator = new Validator();



    /**
     * N.B. The host component may be calling this from its
     * constructor, as in the case of the Frame.  In this case the AWT
     * Component has none or few of of its normal (internal) field
     * values -- like a graphics configuration.
     */
    public Scene(Dictionary<String,String> properties, Application application, 
                   java.awt.Container hostContainer, java.awt.Component hostComponent)
    {
        super();
        this.auto = false;
        this.border.none();//(fill but not stroke)

        if (null != properties && null != application && null != hostContainer && null != hostComponent){
            this.properties = Install(properties);
            this.application = application;
            this.hostContainer = hostContainer;

            if (hostContainer instanceof wtkx.in.DisplayContainer)
                this.hostContainerDc = (wtkx.in.DisplayContainer)hostContainer;

            this.hostComponent = hostComponent;
            try {
                this.documentBase = new URL("file:");
            }
            catch (java.net.MalformedURLException exc){
            }
        }
        else
            throw new IllegalArgumentException();
    }
    public Scene(Dictionary<String,String> properties, Application application, 
                   java.awt.Container hostContainer, java.awt.Component hostComponent, URL documentBase)
    {
        this(properties,application,hostContainer,hostComponent);
        this.documentBase = documentBase;
    }


    public void init(Awtk awtk){
        if (null != awtk){
            if (null == this.awtk){

                this.awtk = awtk;

                Dimension hz = this.hostContainer.getSize();

                this.setSize(hz.width,hz.height);
            }
            else
                throw new IllegalStateException();
        }
        else
            throw new IllegalArgumentException();
    }
    public void startup(){
        Application application = this.application;
        if (null != application){
            this.requestFocus();
            try {
                application.startup(this,this.properties);
            }
            catch (Exception exc) {
                exc.printStackTrace();
                this.alertStop(exc);
            }
        }
    }
    public void shutdown(){
        Application application = this.application;
        if (null != application){
            try {
                application.shutdown();
            }
            catch (Exception exc) {
                exc.printStackTrace();
            }
        }
    }
    public void suspend(){
        Application application = this.application;
        if (null != application)
            try {
                application.suspend();
            }
            catch (Exception exc) {
                exc.printStackTrace();
            }
    }
    public void resume(){
        Application application = this.application;
        if (null != application)
            try {
                application.resume();
            }
            catch (Exception exc) {
                exc.printStackTrace();
            }
    }
    public void requestFocus(){
        this.hostComponent.requestFocusInWindow();
    }
    public boolean transferFocus(Direction direction) {

        for (Component child : this.inverse()){
            if (child.findFocus(direction))
                return true;
        }

        return false;
    }
    public void destroy(){
        this.properties = null;
        this.hostContainer = null;
        this.hostContainerDc = null;
        this.hostComponent = null;
        this.awtk = null;
        super.destroy();
    }
    public Dictionary<String, String> getProperties(){
        return properties;
    }
    public java.awt.Container getHostContainer() {
        return this.hostContainer;
    }
    public java.awt.Component getHostComponent() {
        return this.hostComponent;
    }
    public Scene getScene() {
        return this;
    }
    public Screen getScreen(){
        Awtk awtk = this.awtk;
        if (null != awtk)
            return awtk.getScreen();
        else
            return null;
    }
    public Awtk getAwtk(){
        return this.awtk;
    }
    public URL getDocumentBase() {
        URL documentBase = this.documentBase;
        if (null == documentBase)
            throw new IllegalStateException();
        else
            return documentBase;
    }
    public ResourceCacheDictionary getResourceCache() {
        return resourceCache;
    }
    public java.awt.Robot getRobot()
        throws java.awt.AWTException
    {
        Awtk awtk = this.awtk;
        if (null != awtk)
            return awtk.getRobot();
        else
            return null;
    }
    public Point getMouseLocation() {
        Awtk awtk = this.awtk;
        if (null != awtk)
            return awtk.getMouseLocation();
        else
            return null;
    }
    public void setCursor(java.awt.Cursor cursor){
        this.hostComponent.setCursor(cursor);
    }
    public java.awt.Cursor getCursorNative(){
        return hostComponent.getCursor();
    }
    public URL getInputURL(String path){
        if (this.hasDocumentBase()){
            URL documentBase = this.getDocumentBase();
            try {
                return new URL(documentBase,path);
            }
            catch (java.net.MalformedURLException exc){
                exc.printStackTrace();
            }
        }
        wtkx.in.DisplayContainer dc = this.hostContainerDc;
        if (null != dc)
            return dc.getInputURL(path);
        else
            return null;
    }
    public void setParent(Component parent) {
        throw new UnsupportedOperationException();
    }
    @Override
    public void setLocation(float x, float y) {
        throw new UnsupportedOperationException("Can't change the location of the scene.");
    }
    @Override
    public void invalidate() {

        super.invalidate();

        Threads.QueueCallback(this.validator);
    }
    @Override
    public void validate() {

        super.validate();

        this.repaint();
    }

    public void repaint() {
        Awtk awtk = this.awtk;
        if (null != awtk)
            awtk.repaint();

    }
    public void repaint(float x, float y, float w, float h) {

        if (0f >= w || 0f >= h){

            Awtk awtk = this.awtk;
            if (null != awtk)
                awtk.repaint();
        }
        else {
            Awtk awtk = this.awtk;
            if (null != awtk)
                awtk.repaint((int)x, (int)y, (int)w, (int)h);
        }
    }
    /**
     * The scene graph is painted from the host component.
     */
    public void paint(Graphics2D g) {

        g.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING,
                           GV.RenderTextAliasing);

        g.setRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS,
                           GV.RenderTextFraction);

        g.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
                           GV.RenderAntialiasing);

        Graphics2D gd = this.effects.prepare(this,g);
        try {
            super.paint(gd);
        }
        finally {
            if (gd != g)
                gd.dispose();
        }
        this.effects.update(g);
    }
    public Font getFont(){
        Font font = this.font;
        if (null == font){
            String string = this.properties.get("font");
            this.setFont(string);
            return super.getFont();
        }
        return font;
    }
    public FontMetrics getFontMetrics(){
        FontMetrics fontMetrics = this.fontMetrics;
        if (null == fontMetrics){
            Font font = this.getFont();
            if (null != font){
                Toolkit tk = Toolkit.getDefaultToolkit();
                fontMetrics = tk.getFontMetrics(font);
                this.fontMetrics = fontMetrics;
            }
        }
        return fontMetrics;
    }
    public Color getColor(){
        Color color = this.color;
        if (null == color){
            String string = this.properties.get("color");
            if (null != string){
                this.setColor(string);
                return super.getColor();
            }
            else if (null != this.hostComponent){
                color = this.hostComponent.getForeground();
                this.setColor(string);
                return color;
            }
        }
        return color;
    }
    public Color getBackgroundColor(){
        Color backgroundColor = this.backgroundColor;
        if (null == backgroundColor){
            String string = this.properties.get("background-color");
            if (null != string){
                this.setBackgroundColor(string);
                return super.getBackgroundColor();
            }
            else if (null != this.hostComponent){
                backgroundColor = this.hostComponent.getBackground();
                this.setBackgroundColor(string);
                return color;
            }
        }
        return backgroundColor;
    }
    public Color getSelectionColor(){
        Color selectionColor = this.selectionColor;
        if (null == selectionColor){
            String string = this.properties.get("selection-color");
            this.setSelectionColor(string);
            return super.getSelectionColor();
        }
        return selectionColor;
    }
    public float getWidth(){
        float width = this.width;
        if (0f == width){
            String string = this.properties.get("width");
            if (null != string){
                this.setWidth(string);
                return super.getWidth();
            }
            else if (null != this.hostComponent) {
                width = this.hostComponent.getWidth();
                this.setWidth(string);
                return width;
            }
        }
        return width;
    }
    public float getHeight(){
        float height = this.height;
        if (0f == height){
            String string = this.properties.get("height");
            if (null != string){
                this.setHeight(string);
                return super.getHeight();
            }
            else if (null != this.hostComponent) {
                height = this.hostComponent.getHeight();
                this.setHeight(string);
                return height;
            }
        }
        return height;
    }
}
